﻿# -*- coding: utf-8 -*-
""" A module to choose column fields from an ASCII file and read them into a
numpy array.

    Module has a wizard class to choose interactively the columns and a
    funcion to read ASCII numbers into a numpy array.

    :Author:
      - 20111202-20120214 Roberto Vidmar

    :Revision:  $Revision: 57 $
                $Date: 2012-11-23 14:34:10 +0000 (Fri, 23 Nov 2012) $

    :Copyright: 2011-2012
                Nicola Creati <ncreati@inogs.it>
                Roberto Vidmar <rvidmar@inogs.it>

    :License: MIT/X11 License (see :download:`license.txt
                               <../../license.txt>`)

    ASCII files we wuold like to read are like this:

>>>
45°38'49.879" 13°45'34.397" 52.80
13d45.573283' 45d38.831317' 52.80
13.759554722 45.647188611 52.80
393163.59 5088931.03 127.29 222.22
393163.35°5088929.23 130.70
393164.35 5088930.35 127.65
393165.54 5088930.29 129.22
393164.43 5088929.26 129.98
393165.54 5088930.29 129.22
"""

import sys
import os.path
import codecs
import cPickle
import numpy as np
#import time

from qtCompat import QtCore, Signal, QtGui, getOpenFileName, getSaveFileName
QCA = QtCore.QCoreApplication

from degrees import dms2deg

def countLines(pn):
  """ Return number of lines in ASCII file pn

  :param pn: Pathname of the file
  :type pn: unicode or str
  :returns: the number of lines in the file
  :rtype: int
  :raises:
  """
  lines = 0
  f = open(pn, 'r')
  lines = 0
  buf_size = 1024 * 1024
  read_f = f.read # loop optimization

  buf = read_f(buf_size)
  while buf:
    lines += buf.count('\n')
    buf = read_f(buf_size)

  f.close()
  return lines

def readASCII(pn, names, formats, headerlines=(), removeEmptyLines=False,
  commentChars=''):
  """ Return numpy array from ASCII input file pn according to numpy dtype.

      - Empty lines are removed according to removeEmptyLines flag.
      - If commentChars is not empty, lines beginning with commentChars will
        be removed.


      * **No check is made on `names` or `formats`: these MUST be valid to
        create a numpy.dtype.**

    :param pn: pathname of the file to write
    :type pn: string, unicode
    :param names: dictionary of names to create numpy dtype
    :type names: dictionary
    :param formats: dictionary of formats to create numpy dtype
    :type formats: dictionary
    :param headerlines: first and last line of an optional header
    :type headerlines: tuple
    :param removeEmptyLines: if True empty lines will be removed from file
      `before` any other processing
    :type removeEmptyLines: bool
    :param commentChars: characters to be treated as comments at the
      beginning of line
    :type commentChars: string
    :returns: numpy array from ASCII input according to numpy dtype
      defined by (names, formats)
    :rtype: tuple
    :raises:
  """
  # Start timer
  #startTime = time.time()
  #tic = time.time()

  infile = codecs.open(pn, 'r', 'UTF-8')
  wholefile = infile.readlines()
  infile.close()
  # 0.53 s for 1014694 lines
  #print "readlines: %.2f s." % (time.time() - tic)

  # Get rid of comments and empty lines
  if commentChars and removeEmptyLines:
    test = lambda x: x.endswith(commentChars) or x.isspace()
  elif commentChars and not removeEmptyLines:
    test = lambda x: x.endswith(commentChars)
  elif not commentChars and removeEmptyLines:
    test = lambda x: x.isspace()
  else:
    test = None

  if test is not None:
    #tic = time.time()
    for line in wholefile:
      if test(line):
        del line
    # 0.28 s for 1014694 lines
    #print "Remove empty lines: %.2f s." % (time.time() - tic)

  #tic = time.time()
  if headerlines:
    hstart, hend = headerlines
    wholefile = np.hstack((wholefile[:hstart], wholefile[hend:]))
  wholefile = [line.encode('ascii', 'replace') for line in wholefile]
  data = np.asarray(wholefile,
    dtype=np.dtype({'names': names, 'formats': formats}))
  # 0.84 s for 1014694 lines
  #print "Asarray: %.2f s." % (time.time() - tic)

  # Total time is 1.65 s for 1014694 lines
  #print "fileToArray: %.2f s." % (time.time() - startTime)
  return data

#===============================================================================
class FieldSelectionDialog(QtGui.QDialog):
  """ A Field selection dialog with field names and radio buttons.
      The number of columns is driven by fieldsTable.
  """
  def __init__(self, fieldsTable, requiredIndexes, selectedFields):
    """ Create a new FieldSelectionDialog instance.

        fieldsTable usually is a table like these:

        >>>
          [
          [u'Longitude', u'D.DDD', u'D', u'M.MMM', u'M', u'S.SSS'],
          [u'Latitude',  u'D.DDD', u'D', u'M.MMM', u'M', u'S.SSS'],
          [u'Height',    u'H.HHH', orthoDef]
          ]
        or
          [
          [u'Easting',  u'M.MMM',],
          [u'Northing', u'M.MMM',],
          [u'Height',   u'H.HHH', orthoDef],
          ]

      requiredIndexes usually is a table like these:

      >>>
        (
        (set([1]), set([2, 3]), set([2, 4, 5])),
        (set([1]), set([2, 3]), set([2, 4, 5])),
        (set([1]), set([2])),
        )
      or
        (
        (set([1]),),
        (set([1]),),
        (set([1]), set([2])),
        )

      :param fieldsTable: the table definining the fields to choose from
      :type fieldsTable: list
      :param requiredIndexes: the table definining the minimum requirements
                              of the choice
      :type requiredIndexes: list
      :param selectedFields: the fields already selected
      :type selectedFields: list
    """
    # Initialize the Dialog
    super(FieldSelectionDialog, self).__init__()

    dLayout = QtGui.QVBoxLayout()
    label = QtGui.QLabel(QCA.translate("FieldSelectionDialog",
      "This column range is:"))
    dLayout.addWidget(label)
    font = QtGui.QFont("Courier New", 13, QtGui.QFont.Bold)

    gLayout = QtGui.QGridLayout()
    self.bg = QtGui.QButtonGroup(self)

    # Get the field names for all columns
    fieldNames = [i[0] for i in fieldsTable]

    for icol, colName in enumerate(fieldNames):
      # Add the label for the column
      gLayout.addWidget(QtGui.QLabel(colName), 0, icol)
      selected = set()
      btns = []
      # Now the sub field names for the column
      for irow, fieldName in enumerate(fieldsTable[icol][1:]):
        if not fieldName:
          continue
        s = (str(icol), fieldName)
        btn = QtGui.QRadioButton(fieldName)
        btn.setObjectName(str(icol))
        btns.append(btn)
        btn.setFont(font)
        self.bg.addButton(btn)
        gLayout.addWidget(btn, irow + 1, icol)
        # Disable it if already selected
        if s in selectedFields.values():
          btn.setEnabled(False)
          selected.add(irow + 1)

      if selected:
        # Column has been built. Check if we can reduce the choice:
        selectable = set()
        for required in requiredIndexes[icol]:
          if selected.issubset(required):
            selectable = selectable.union(required)
        allFields = set(range(1, irow + 2))
        unselectable = allFields.difference(selectable)
        for i in unselectable:
          try:
            btns[i - 1].setEnabled(False)
          except IndexError:
            pass

    self.bg.setExclusive(True)

    dLayout.addLayout(gLayout)
    self.setLayout(dLayout)
    self.bg.buttonClicked.connect(self.close)

  def selectedBtn(self):
    """ Return checkedButton

    :returns: checkedButton
    :rtype: QtGui.QRadioButton
    :raises:
    """
    return self.bg.checkedButton()

#===============================================================================
class FileBrowserWidget(QtGui.QListWidget):
  """ A QListWidget with a header selector
  """
  lineSelectedSignal = Signal(object)

  def __init__(self, *args, **kargs):
    """ Create a new FileBrowserWidget instance.

        * First argument must be the file content, all other arguments
          are sent to the constructor of QtGui.QListWidget

      :param \*args: file content
      :type \*args: list of strings
      :param \*kargs: optional keyword arguments
      :type \*kargs: various
      :raises:
    """
    self.headerLines = ()
    self._toolTip = QCA.translate("fileBrowserWidget",
      "This is a line of the file")
    super(FileBrowserWidget, self).__init__(*args[1:], **kargs)
    self.addItems(args[0])
    self._flags = self.itemAt(0, 0).flags()

  def contextMenuEvent(self, evt):
    """ Intercept right mouse click to display a field selection dialog.

      :param evt: an event
      :type evt: event
      :raises:
    """
    if self.selectionMode() == QtGui.QAbstractItemView.ContiguousSelection:
      # Select Header
      rows = [self.row(i) for i in self.selectedItems()]
      rmin, rmax = (min(rows), max(rows))
      if rmin:
        msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning,
          QCA.translate("fileBrowserWidget", 'Header Error'),
          QCA.translate("fileBrowserWidget",
          "Header must start from line one,\nnot from line %d.\n"
          "Please make another selection.") % (rmin + 1),
          QtGui.QMessageBox.Ok)
        msgBox.exec_()
        return

      # Ok, selection is valid
      self.headerLines = (rmin, rmax + 1)
      self.selectionIsValid()
    else:
      item = self.selectedItems()[0]
      item.setToolTip(QCA.translate("fileBrowserWidget",
        "This is the selected line"))
      self.lineSelectedSignal.emit(item.text())

  def selectionIsValid(self):
    self.grayOutHeaderItems(True)
    self.parent().headerSelected(True)
    self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)

  def grayOutHeaderItems(self, grayout):
    if grayout:
      # Items are "frozen"
      flags = QtCore.Qt.NoItemFlags
      toolTip = QCA.translate("fileBrowserWidget",
        "This line belongs to the Header")
    else:
      # Restore Flags
      flags = self._flags
      toolTip = self._toolTip
    for i in [self.item(row) for row in xrange(*self.headerLines)]:
      i.setFlags(flags)
      i.setToolTip(toolTip)

  def setContiguousSelection(self, hasHeader):
    if hasHeader:
      self.setSelectionMode(QtGui.QAbstractItemView.ContiguousSelection)
    else:
      self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)

#===============================================================================
class FieldsSelector(QtGui.QLabel):
  """ A QLabel with a column selector wizard
  """
  newFieldSignal = Signal(object)

  def __init__(self, *args, **kargs):
    """ Create a new FieldsSelector instance.

        * The optional args and kargs are fed to the base class constructor.

      :param \*args: optional arguments
      :type \*args: various
      :param \*kargs: optional keyword arguments
      :type \*kargs: various
      :raises:
    """
    self._fields = {}
    super(FieldsSelector, self).__init__(*args, **kargs)

  def rangeOverlaps(self, start, end):
    """ Return True if this range overlaps another range already selected

      :param start: start of the range
      :type start: int
      :param end: end of the range
      :type end: int
      :returns: True if this range overlaps another range already selected
      :rtype: bool
      :raises:
    """
    for value in self._fields.itervalues():
      s, e = value
      if s <= start <= e or s <= end <= e:
        return True
    return False

  def contextMenuEvent(self, evt):
    """ Intercept right mouse click to display a field selection dialog

      :param evt: an event
      :type evt: event
      :raises:
    """
    # The selection
    selection = unicode(self.selectedText())
    start = int(self.selectionStart())
    end = int(start + len(selection) - 1)
    if self.rangeOverlaps(start, end):
      msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning,
        QCA.translate("fieldsSelector", 'Range Overlap Error'),
        QCA.translate("fieldsSelector",
          "This selection overlaps another one.\nMake another selection."),
        QtGui.QMessageBox.Ok)
      msgBox.exec_()
      return

    # Ok, Show the field selection dialog
    cmd = FieldSelectionDialog(self.parent().fieldsTable,
      self.parent().requiredIndexes, self._fields)
    cmd.exec_()
    try:
      btn = cmd.selectedBtn()
      fName = (btn.objectName(), btn.text())
      colRange = (start, end)
      self._fields[colRange] = fName
      self.newFieldSignal.emit(colRange)
    except AttributeError:
      pass

  def selection(self):
    """ Return defined fields, columns dictionary

    :returns: defined fields, i.e. a columns dictionary
    :rtype: dict
    :raises:
    """
    return self._fields

  def resetSelection(self):
    """ Reset selection to an empty dict
    """
    self.setText("")
    self._fields = {}

#===============================================================================
class FieldsWizard(QtGui.QDialog):
  """ A list driven ASCII file wizard

    :Author:
      - 20120123 Roberto Vidmar

    The wizard permits to choose fields from a field table.

    The table is a list of fields where every "field" is a list
    of "field name", {"sub field name",}

    A required indexes list is necessary to specify which sub fields are
    necessary to identify the field.

    A selectionDoneSignal is emitted when the selection satisfies the
    minimum requirements.

    selectionDoneSignal carries a dictionary with the following items:
     - pathName: String
     - header:   A tuple (firstRow, lastRow) defining the (optional) header
     - names:    A list defining field names
     - formats:  A list defining field formats
  """
  # Constants
  LinesToRead = 30

  # Signals
  selectionDoneSignal = Signal(object)

  def __init__(self, *args, **kargs):
    """ Create a new FieldsWizard instance.


     The following keyword arguments are understood:

      - pathName: the pathname of the file to read
                  Default is ''
      - fieldsTable: a table of field names and the subfields
                     Default is []
      - requiredIndexes: a table of required indexes
                         Default is []
      - longLat: if True or False use default fieldsTable and requiredIndexes.
                 Default is None
      - ortho: if not None, orthometric height can be specified.
               Default is None
      - fmtExt: extension for format files
                Default is 'fmt'

    Default `fieldsTable` and `requiredIndexes` are:

    >>>
    longLat:
      self.fieldsTable = [
        [u'Longitude', u'D.DDD', u'D', u'M.MMM', u'M', u'S.SSS'],
        [u'Latitude',  u'D.DDD', u'D', u'M.MMM', u'M', u'S.SSS'],
        [u'Height',    u'H.HHH', orthoDef]
        ]
      self.requiredIndexes = (
        (set([1]), set([2, 3]), set([2, 4, 5])),
        (set([1]), set([2, 3]), set([2, 4, 5])),
        (set([1]), set([2])),
        )

    longLat is not None:
      self.fieldsTable = [
        [u'Easting',  u'M.MMM',],
        [u'Northing', u'M.MMM',],
        [u'Height',   u'H.HHH', orthoDef],
        ]
      self.requiredIndexes = (
        (set([1]),),
        (set([1]),),
        (set([1]), set([2])),
        )

    Where orthoDef = u'O.OOO' if Geoid is available (ortho is not None)
    else: orthoDef = u''

      :param \*args: optional arguments
      :type \*args: various
      :param \*kargs: optional keyword arguments
      :type \*kargs: various
      :raises:
    """
    # Keyword arguments
    self._pathName = kargs.pop('pathName', '')
    self.fieldsTable = kargs.pop('fieldsTable', [])
    self.requiredIndexes = kargs.pop('requiredIndexes', ())
    longLat = kargs.pop('longLat', None)
    ortho = kargs.pop('ortho', None)
    fmtExt = kargs.pop('fmtExt', 'fmt')
    self.fmtExt = os.path.extsep + fmtExt
    self.flt = QCA.translate("fieldsWizard",
      "Format files (*%s);;All Files (*%s*)") % (
      self.fmtExt, os.path.extsep)

    if ortho is not None:
      # Geoid is available
      orthoDef = u'O.OOO'
    else:
      orthoDef = u''

    if longLat:
      self.fieldsTable = [
        [u'Longitude', u'D.DDD', u'D', u'M.MMM', u'M', u'S.SSS'],
        [u'Latitude',  u'D.DDD', u'D', u'M.MMM', u'M', u'S.SSS'],
        [u'Height',    u'H.HHH', orthoDef]
        ]
      self.requiredIndexes = (
        (set([1]), set([2, 3]), set([2, 4, 5])),
        (set([1]), set([2, 3]), set([2, 4, 5])),
        (set([1]), set([2])),
        )
    elif longLat is not None:
      self.fieldsTable = [
        [u'Easting',  u'M.MMM',],
        [u'Northing', u'M.MMM',],
        [u'Height',   u'H.HHH', orthoDef],
        ]
      self.requiredIndexes = (
        (set([1]),),
        (set([1]),),
        (set([1]), set([2])),
        )
    assert(len(self.fieldsTable) == len(self.requiredIndexes))

    # Nothing selected
    self.names = []
    self.formats = []

    # The "flavour" of our selection according to requiredIndexes
    self.flavour = [None for m in self.requiredIndexes]

    # Read first at most LinesToRead lines from file
    lines = countLines(self._pathName)
    ###print _("File %s contains %s lines.") % (self._pathName, lines)

    with codecs.open(self._pathName, 'r', 'UTF-8') as inputFile:
      fileLines = [inputFile.next()
        for x in xrange(min(self.LinesToRead, lines))]

    fileContent = [line.strip() for line in fileLines]

    #    "background-color: blanchedalmond;"
    #    "background-color: rgba(255,   0,   0, 255);"

    # Create the Widget
    super(FieldsWizard, self).__init__(*args, **kargs)
    formStyleSheet = (
        "QLabel#Form{"
        "font: bold 20pt Courier New;"
        "padding: 1px;"
        "border-style: solid;"
        "border: 2px solid gray;"
        "border-radius: 8px;"
        "}")
    labelStyleSheet = (
        # 0 = transparent
        "QLabel#FieldsSelector{"
        "font: bold 20pt Courier New;"
        "background-color: blanchedalmond;"
        "padding: 1px;"
        "border-style: solid;"
        "border: 2px solid gray;"
        "border-radius: 8px;"
        "}")

    # Load Format Button
    self.loadFormatBtn = QtGui.QPushButton(QCA.translate("fieldsWizard",
      "Load From File"))
    self.loadFormatBtn.setEnabled(True)
    self.loadFormatBtn.clicked.connect(self._loadFormat)

    # Done Button
    self.doneBtn = QtGui.QPushButton(QCA.translate("fieldsWizard", "Done"))
    self.doneBtn.setEnabled(False)
    self.doneBtn.clicked.connect(self._done)

    # Reset Button
    self.resetBtn = QtGui.QPushButton(QCA.translate("fieldsWizard", "Reset"))
    self.resetBtn.setEnabled(False)
    self.resetBtn.clicked.connect(self._reset)

    # Has Header Checkbox
    self.headerChk = QtGui.QCheckBox()
    self.headerSelected(False)
    self.headerChk.stateChanged.connect(self._hasHeader)

    # The Form
    self.form = QtGui.QLabel()
    self.form.setObjectName('Form')
    self.form.setStyleSheet(formStyleSheet)
    self.form.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)

    # The Fields Selector widget
    self.fieldsSelector = FieldsSelector(self)
    self.fieldsSelector.setObjectName('FieldsSelector')
    self.fieldsSelector.setStyleSheet(labelStyleSheet)
    self.fieldsSelector.setTextInteractionFlags(
      QtCore.Qt.TextSelectableByMouse| QtCore.Qt.TextSelectableByKeyboard)

    # The File Browser
    self.fileBrowser = FileBrowserWidget(fileContent)
    self.fileBrowser.lineSelectedSignal.connect(self._lineSelected)
      #QtGui.QAbstractItemView.SingleSelection)
    #self.fileBrowser.setEnabled(False)

    layout = QtGui.QGridLayout()
    layout.addWidget(QtGui.QLabel(
      "Select fields with mouse or keyboard or:"), 0, 0)
    layout.addWidget(self.loadFormatBtn, 0, 1)
    layout.addWidget(self.form, 1, 0)
    layout.addWidget(self.fieldsSelector, 1, 0)
    layout.addWidget(self.fileBrowser, 2, 0)
    layout.addWidget(self.headerChk, 2, 1)
    layout.addWidget(self.doneBtn, 3, 0)
    layout.addWidget(self.resetBtn, 3, 1)
    self.setLayout(layout)
    self.setWindowTitle("Fields Wizard on '%s'" %
      os.path.basename(self._pathName))

    #Signals
    self.fieldsSelector.newFieldSignal.connect(self._newField)

  def headerSelected(self, selected):
    if selected:
      self.headerChk.setText(QCA.translate("fieldsWizard", "Header defined"))
      self.headerChk.setEnabled(False)
      self.headerChk.setToolTip(QCA.translate("fieldsWizard",
        "Input file has a header.\n"
        "Header has been defined."))
    else:
      self.headerChk.setText(QCA.translate("fieldsWizard",
        "This file\nhas a Header"))
      self.headerChk.setEnabled(True)
      self.headerChk.setChecked(False)
      self.headerChk.setToolTip(
        QCA.translate("fieldsWizard",
        "Check this box if input\nfile has a header.\n"
        "Then select header lines\nand right click to save."))

  def updateSelection(self, colRange):
    """ Show underscores below the selected columns for the new field selected

      :param colRange: first column, last column
      :type colRange: tuple
      :raises:
    """
    start, end = colRange
    # 95 is '_'
    sel = chr(95) * (end - start + 1)
    newText = self.form.text()[:start] + sel + self.form.text()[end + 1:]
    self.form.setText(newText)

  def _newField(self, colRange):
    # Enable Reset Button
    self.resetBtn.setEnabled(True)
    # Update selection:
    self.updateSelection(colRange)

    # Do we have all mandatory fields?
    for icol, col in enumerate(self.fieldsTable):
      for ireq, required in enumerate(self.requiredIndexes[icol]):
        fields = self.fieldsSelector.selection().values()
        # fields are (icol, fname)
        selected = ([col.index(i[1]) for i in fields if i[0] == str(icol)])
        selected = set(selected)
        if required.issubset(selected):
          self.flavour[icol] = ireq
          if not None in self.flavour:
            # All required fields have been selected: enable Done button
            self.doneBtn.setEnabled(True)

  def _done(self):
    s = self.fieldsSelector.selection()
    # flavour is currently unused
    #self.flavour

    # Sort columns:
    fieldsranges = sorted(s.keys())

    # Create fields name
    fieldsnames = []
    for col in fieldsranges:
      field = s[col]
      icol = int(field[0])
      name = self.fieldsTable[icol][0] + field[1]
      fieldsnames.append(name)

    # Build formats list suitable for numpy dtype:
    # formats are like this: ['S9', 'V1', 'S10', 'S7', 'S8']
    self.formats = []
    if fieldsranges[0][0] > 0:
      # Start with dummy
      self.formats.append("V%d" % fieldsranges[0][0])

    nfields = len(fieldsranges)
    for i in range(nfields):
      self.formats.append("S%d" % (fieldsranges[i][1] - fieldsranges[i][0] + 1))
      if i < nfields - 1:
        gap = fieldsranges[i + 1][0] - fieldsranges[i][1] - 1
        if gap > 0:
          # Dummy field
          self.formats.append("V%d" % gap)

    # Build names list: we must skip dummy fields as well!
    # names are like this: ['LatitudeD', 'v0', 'LatitudeM.MMM', 'v1']
    self.names = []
    i = 0
    k = 0
    for f in self.formats:
      if f.startswith('S'):
        self.names.append(fieldsnames[i])
        i += 1
      else:
        self.names.append('v%d' % k)
        k += 1
    # Save Selection ?
    folder = QtCore.QDir.currentPath()
    pn = getSaveFileName(self,
      QCA.translate("fieldsWizard", "Save this format to file"),
      folder,
      filter=self.flt, options=QtGui.QFileDialog.DontUseNativeDialog)

    if pn:
      if not pn.endswith(self.fmtExt):
        pn += self.fmtExt
      cPickle.dump((self.fileBrowser.headerLines, self.names, self.formats),
        open(pn, 'w'), 2)

    #self.selectionDoneSignal.emit(self.fileBrowser.headerLines, self.names,
    #  self.formats)
    self.selectionDone()

  def _lineSelected(self, text):
    self.fieldsSelector.setText(text)
    #self.form.setText(text)
    #self.form.setText("jhgasjdfgjasgfjhasgfjhsgjfgsdjfgsdfjk")
    self.fileBrowser.setEnabled(False)

  def _loadFormat(self):
    """ Load format from file
    """
    folder = QtCore.QDir.currentPath()
    pn = getOpenFileName(self,
      QCA.translate("fieldsWizard", "Load format from file"), folder,
      filter=self.flt, options=QtGui.QFileDialog.DontUseNativeDialog)

    if pn:
      self.fileBrowser.headerLines, self.names, self.formats = (
        cPickle.load(open(pn, 'r')))
      if self.fileBrowser.headerLines:
        self.fileBrowser.selectionIsValid()
      #####self.doneBtn.setEnabled(True)
      self.resetBtn.setEnabled(True)
      #self.selectionDoneSignal.emit(self.fileBrowser.headerLines, self.names,
      #  self.formats)
      self.selectionDone()

  def selection(self):
    """ Return current selection dictionaries: names and formats.

      :returns: names and formats
      :rtype: tuple of dicts
      :raises:
    """
    return self.names, self.formats

  def selectionDone(self):
    self.selectionDoneSignal.emit(dict(pathName=self._pathName,
      headerLines=self.fileBrowser.headerLines, names=self.names,
      formats=self.formats))

  def _hasHeader(self, state):
    if state == QtCore.Qt.Checked:
      self.fileBrowser.setContiguousSelection(True)
    else:
      self.fileBrowser.setContiguousSelection(False)

  def _reset(self):
    self.form.setText("")
    self.fieldsSelector.resetSelection()
    self.flavour = [None for m in self.requiredIndexes]
    self.resetBtn.setEnabled(False)
    self.doneBtn.setEnabled(False)
    self.fileBrowser.setEnabled(True)
    self.fileBrowser.grayOutHeaderItems(False)
    self.headerSelected(False)

  def data2Degrees(self, data, names):
    """ Return data converted to degrees

      :param data: dataset to convert
      :type data: numpy array
      :param names: field names
      :type names: dict
      :returns: data converted to degrees
      :rtype: tuple of numpy arrays
      :raises:
    """

    def sort(longlat):
      if len(longlat) == 1:
        ordered = longlat
      else:
        ordered = [None for n in longlat]
        for x in longlat:
          if x.endswith('D'):
            ordered[0] = x
          elif x.endswith('M'):
            ordered[1] = x
          else:
            ordered[2] = x
      return ordered

    xyz = [r[0] for r in self.fieldsTable]
    # Get all xNames
    xNames = [name for name in names if xyz[0] in name]
    # Get all yNames
    yNames = [name for name in names if xyz[1] in name]
    # Get height
    heights = [name for name in names if xyz[2] in name]

    x = [np.asarray(data[:][name], 'float64') for name in sort(xNames)]
    y = [np.asarray(data[:][name], 'float64') for name in sort(yNames)]
    z = np.asarray(data[:][heights[0]], 'float64')

    isOrtho = heights[0].endswith('O')
    return dms2deg(*x), dms2deg(*y), z, isOrtho

#===============================================================================
if __name__ == "__main__":
  import time

  def done(d):

    names = d['names']
    formats = d['formats']
    print "names=", names
    print "formats=", formats
    # Now he have both names and formats: we can save them for later use

    tic = time.time()
    data = readASCII(d['pathname'], names, formats, d['headerLines'])
    xd, yd, z, isOrtho = form.data2Degrees(data, names)
    # 3.22 s for 1014694 lines
    print "\nTotal time to convert file to degrees = %.2f s." % (
      time.time() - tic)

  import signal
  signal.signal(signal.SIGINT, signal.SIG_DFL)

  longlat = True
  ortho = True

  pn = __file__

  app = QtGui.QApplication(sys.argv)
  form = FieldsWizard(pathName=pn, longLat=longlat, ortho=ortho)
  form.selectionDoneSignal.connect(done)
  form.resize(400, 300)
  form.show()
  app.exec_()
